var handleWebsocket = require("./https").handleWebsocket;
var hparser = require("hparser");
var net = require("net");
var Buffer = require("safe-buffer").Buffer;
var pluginMgr = require("./plugins");
var util = require("./util");
var config = require("./config");

var formatHeaders = hparser.formatHeaders;
var getRawHeaderNames = hparser.getRawHeaderNames;
var getRawHeaders = hparser.getRawHeaders;
var PLUGIN_PATH_RE =
	/^\/(\.\.\.whistle-path\.5b6af7b9884e1165\.\.\.\/\/\/)?(whistle|plugin)\.([^/?#]+)\/?/;

function getPluginNameByReq(req, callback) {
	if (!req) {
		return callback();
	}
	var host = req.headers.host;
	var index = host.indexOf(":");
	var port = req.isHttps ? 443 : 80;
	if (index !== -1) {
		port = host.substring(index + 1);
		host = host.substring(0, index);
	}
	var isUIPort = port == config.port || port == config.uiport;
	var isWebUI = isUIPort && util.isLocalHost(host);
	if (!isWebUI && (isWebUI = config.isWebUIHost(host)) && net.isIP(host)) {
		isWebUI = isUIPort;
	}
	var pluginName;
	var isPluginUrl;
	var internalPath;
	if (!config.pureProxy && PLUGIN_PATH_RE.test(req.url)) {
		internalPath = RegExp["$&"];
		req.isInternalUrl = !!RegExp.$1;
		isPluginUrl = RegExp.$2 === "plugin";
		pluginName = RegExp.$3;
	} else if (!isWebUI) {
		pluginName = (isUIPort || !net.isIP(host)) && config.getPluginNameByHost(host);
	}
	if (!pluginName) {
		return callback();
	}
	var plugin = isPluginUrl
		? pluginMgr.getPluginByName(pluginName)
		: pluginMgr.getPlugin(pluginName + ":");
	if (!isWebUI && !plugin) {
		return callback();
	}
	if (internalPath) {
		req.url = "/" + req.url.substring(internalPath.length);
	}
	pluginMgr.loadPlugin(plugin, function (err, ports) {
		var uiPort = ports && ports.uiPort;
		if (err || !uiPort) {
			var msg = [
				"HTTP/1.1",
				err ? 500 : 404,
				err ? "Internal Server Error" : "Not Found"
			].join(" ");
			err = err ? "<pre>" + util.encodeHtml(err) + "</pre>" : "Not Found";
			var length = Buffer.byteLength(err);
			err = [
				msg,
				"Content-Type: text/html; charset=utf8",
				"Content-Length: " + length,
				"\r\n",
				err
			].join("\r\n");
		}
		callback(err, uiPort);
	});
}

function upgradeHandler(req, socket) {
	util.removeSpecPath(req);
	++util.proc.allWsRequests;
	++util.proc.totalAllWsRequests;
	var done, reqDestroyed, resDestroyed, resSocket;
	function destroy(err) {
		if (resSocket) {
			if (!resDestroyed) {
				resDestroyed = true;
				resSocket.destroy(err);
			}
		} else if (!reqDestroyed) {
			reqDestroyed = true;
			socket.destroy(err);
		}
		if (socket.isLogRequests) {
			--util.proc.wsRequests;
		}
		socket.isLogRequests = false;
		if (!done) {
			done = true;
			--util.proc.allWsRequests;
		}
	}
	var headers = req.headers;
	util.onSocketEnd(socket, destroy);
	util.addTunnelData(socket, headers);
	socket._clientId = util.getComposerClientId(headers);
	var getBuffer = function (method, newHeaders, path) {
		var rawData = [
			(method || "GET") + " " + (path || socket.url || req.url) + " " + "HTTP/1.1"
		];
		newHeaders = formatHeaders(newHeaders || headers, req.rawHeaders);
		rawData.push(getRawHeaders(newHeaders));
		return Buffer.from(rawData.join("\r\n") + "\r\n\r\n");
	};
	var sniPlugin = headers[config.SNI_PLUGIN_HEADER];
	if (sniPlugin) {
		socket.sniPlugin = req.sniPlugin = sniPlugin;
		delete headers[config.SNI_PLUGIN_HEADER];
	}
	req.isPluginReq = socket.isPluginReq = util.checkPluginReqOnce(req);
	util.handleForwardedProps(req);
	var isHttps = socket.isHttps || req.isHttps || !!headers[config.HTTPS_FIELD];
	req.isHttps = socket.isHttps = isHttps;
	// format headers
	req.isWs = true;
	socket.fullUrl = util.getFullUrl(req);
	socket._fwdHost = req._fwdHost;
	getPluginNameByReq(req, function (err, uiPort) {
		if (err) {
			return socket.write(err);
		}
		var clientInfo = util.parseClientInfo(req);
		var clientIp = clientInfo[0] || util.getClientIp(req);
		var clientPort = clientInfo[1] || util.getClientPort(req);
		socket._disabledProxyRules = req._disabledProxyRules;
		socket._remoteAddr = clientInfo[2] || util.getRemoteAddr(req);
		socket._remotePort = clientInfo[3] || util.getRemotePort(req);
		delete headers[config.CLIENT_PORT_HEAD];
		if (uiPort) {
			headers[config.CLIENT_IP_HEAD] = clientIp;
			headers[config.CLIENT_PORT_HEAD] = clientPort;
			headers["x-whistle-remote-address"] = socket._remoteAddr;
			headers["x-whistle-remote-port"] = socket._remotePort;
			headers[config.PLUGIN_HOOK_NAME_HEADER] = config.PLUGIN_HOOKS.UI;
			socket.pause();
			util.connect(
				{
					port: uiPort || config.port,
					host: "127.0.0.1"
				},
				function (err, s) {
					resSocket = s;
					if (err || socket._hasError) {
						return destroy(err);
					}
					resSocket.on("error", destroy);
					resSocket.write(getBuffer(req.method));
					socket.pipe(resSocket).pipe(socket);
					socket.resume();
				}
			);
			return;
		}
		// 不能放到上面，否则转发后的协议将丢失
		delete headers[config.HTTPS_FIELD];
		socket.rawHeaderNames = getRawHeaderNames(req.rawHeaders);
		socket.headers = headers;
		socket.url = req.url;
		socket.fromTunnel = req.fromTunnel;
		socket.fromComposer = req.fromComposer;
		socket.enableXFF = req.enableXFF;
		socket.isInternalUrl = req.isInternalUrl;
		delete socket.headers[config.CLIENT_PORT_HEAD];
		socket.getBuffer = function (newHeaders, path) {
			return getBuffer(null, newHeaders, path);
		};
		handleWebsocket(socket, clientIp, clientPort);
	});
}

module.exports = function (server) {
	server.on("upgrade", upgradeHandler);
};
